Don't place html in alt/title attributes, especially with thumbnails
[lhc/web/wiklou.git] / includes / Image.php
index 479fdcc..96f8c5c 100644 (file)
@@ -47,8 +47,9 @@ class Image
                $type,          # MEDIATYPE_xxx (bitmap, drawing, audio...)
                $mime,          # MIME type, determined by MimeMagic::guessMimeType
                $size,          # Size in bytes (loadFromXxx)
-               $metadata,      # Metadata
-               $dataLoaded;    # Whether or not all this has been loaded from the database (loadFromXxx)
+               $metadata,      # Metadata
+               $dataLoaded,    # Whether or not all this has been loaded from the database (loadFromXxx)
+               $lastError;     # Error string associated with a thumbnail display error
 
 
        /**#@-*/
@@ -211,8 +212,7 @@ class Image
         * Load metadata from the file itself
         */
        function loadFromFile() {
-               global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang,
-                      $wgShowEXIF;
+               global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang, $wgShowEXIF;
                $fname = 'Image::loadFromFile';
                wfProfileIn( $fname );
                $this->imagePath = $this->getFullPath();
@@ -741,8 +741,8 @@ class Image
                        $base = $wgUploadBaseUrl;
                        $path = $wgUploadPath;
                }
-               $url = "{$base}{$path}" .  wfGetHashPath($name, $fromSharedDirectory) . "{$name}";
-               return wfUrlencode( $url );
+               $url = "{$base}{$path}" .  wfGetHashPath($name, $fromSharedDirectory) . "{$name}";
+               return wfUrlencode( $url );
        }
 
        /**
@@ -866,10 +866,7 @@ class Image
 
                if ($this->canRender()) {
                        if ( $width > $this->width * $height / $this->height )
-                               $width = floor( $this->width * $height / $this->height );
-                               # Note this is the largest width such that the thumbnail's
-                               # height is at most $height.
-
+                               $width = wfFitBoxWidth( $this->width, $this->height, $height );
                        $thumb = $this->renderThumb( $width );
                }
                else $thumb= NULL; #not a bitmap or renderable image, don't try.
@@ -911,7 +908,6 @@ class Image
         */
        function renderThumb( $width, $useScript = true ) {
                global $wgUseSquid, $wgInternalServer;
-               global $wgThumbnailScriptPath, $wgSharedThumbnailScriptPath;
                global $wgSVGMaxSize, $wgMaxImageArea, $wgThumbnailEpoch;
 
                $fname = 'Image::renderThumb';
@@ -983,6 +979,7 @@ class Image
                        clearstatcache();
                }
 
+               $done = true;
                if ( !file_exists( $thumbPath ) ||
                        filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) ) {
                        $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).
@@ -1009,7 +1006,10 @@ class Image
                                }
                        }
                        if ( !$done ) {
-                               $this->reallyRenderThumb( $thumbPath, $width, $height );
+                               $this->lastError = $this->reallyRenderThumb( $thumbPath, $width, $height );
+                               if ( $this->lastError === true ) {
+                                       $done = true;
+                               }
 
                                # Purge squid
                                # This has to be done after the image is updated and present for all machines on NFS,
@@ -1025,7 +1025,11 @@ class Image
                        }
                }
 
-               $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
+               if ( $done ) {
+                       $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
+               } else {
+                       $thumb = null;
+               }
                wfProfileOut( $fname );
                return $thumb;
        } // END OF function renderThumb
@@ -1034,14 +1038,20 @@ class Image
         * Really render a thumbnail
         * Call this only for images for which canRender() returns true.
         *
+        * @param string $thumbPath Path to thumbnail
+        * @param int $width Desired width in pixels
+        * @param int $height Desired height in pixels
+        * @return bool True on error, false or error string on failure.
         * @access private
         */
        function reallyRenderThumb( $thumbPath, $width, $height ) {
-               global $wgSVGConverters, $wgSVGConverter,
-                       $wgUseImageMagick, $wgImageMagickConvertCommand;
+               global $wgSVGConverters, $wgSVGConverter;
+               global $wgUseImageMagick, $wgImageMagickConvertCommand;
+               global $wgCustomConvertCommand;
 
                $this->load();
 
+               $err = false;
                if( $this->mime === "image/svg" ) {
                        #Right now we have only SVG
 
@@ -1058,26 +1068,51 @@ class Image
                                        $wgSVGConverters[$wgSVGConverter] );
                                wfProfileIn( 'rsvg' );
                                wfDebug( "reallyRenderThumb SVG: $cmd\n" );
-                               $conv = wfShellExec( $cmd );
+                               $err = wfShellExec( $cmd );
                                wfProfileOut( 'rsvg' );
-                       } else {
-                               $conv = false;
                        }
                } elseif ( $wgUseImageMagick ) {
                        # use ImageMagick
+                       
+                       if ( $this->mime == 'image/jpeg' ) {
+                               $quality = "-quality 80"; // 80%
+                       } elseif ( $this->mime == 'image/png' ) {
+                               $quality = "-quality 95"; // zlib 9, adaptive filtering
+                       } else {
+                               $quality = ''; // default
+                       }
+
                        # Specify white background color, will be used for transparent images
                        # in Internet Explorer/Windows instead of default black.
 
                        # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
                        # It seems that ImageMagick has a bug wherein it produces thumbnails of
                        # the wrong size in the second case.
-                       $cmd  =  $wgImageMagickConvertCommand .
-                               " -quality 85 -background white -size {$width} ".
-                               wfEscapeShellArg($this->imagePath) . " -resize {$width}x{$height} " .
-                               wfEscapeShellArg($thumbPath);
+                       
+                       $cmd  =  wfEscapeShellArg($wgImageMagickConvertCommand) .
+                               " {$quality} -background white -size {$width} ".
+                               wfEscapeShellArg($this->imagePath) .
+                               // For the -resize option a "!" is needed to force exact size,
+                               // or ImageMagick may decide your ratio is wrong and slice off
+                               // a pixel.
+                               " -resize " . wfEscapeShellArg( "{$width}x{$height}!" ) .
+                               " -depth 8 " .
+                               wfEscapeShellArg($thumbPath) . " 2>&1";
                        wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
                        wfProfileIn( 'convert' );
-                       $conv = wfShellExec( $cmd );
+                       $err = wfShellExec( $cmd );
+                       wfProfileOut( 'convert' );
+               } elseif( $wgCustomConvertCommand ) {
+                       # Use a custom convert command
+                       # Variables: %s %d %w %h
+                       $src = wfEscapeShellArg( $this->imagePath );
+                       $dst = wfEscapeShellArg( $thumbPath );
+                       $cmd = $wgCustomConvertCommand;
+                       $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+                       $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
+                       wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
+                       wfProfileIn( 'convert' );
+                       $err = wfShellExec( $cmd );
                        wfProfileOut( 'convert' );
                } else {
                        # Use PHP's builtin GD library functions.
@@ -1134,8 +1169,20 @@ class Image
                        $thumbstat = stat( $thumbPath );
                        if( $thumbstat['size'] == 0 ) {
                                unlink( $thumbPath );
+                       } else {
+                               // All good
+                               $err = true;
                        }
                }
+               if ( $err !== true ) {
+                       return wfMsg( 'thumbnail_error', $err );
+               } else {
+                       return true;
+               }
+       }
+
+       function getLastError() {
+               return $this->lastError;
        }
 
        function imageJpegWrapper( $dst_image, $thumbPath ) {
@@ -1210,6 +1257,10 @@ class Image
        }
 
        function checkDBSchema(&$db) {
+               global $wgCheckDBSchema;
+               if (!$wgCheckDBSchema) {
+                       return;
+               }
                # img_name must be unique
                if ( !$db->indexUnique( 'image', 'img_name' ) && !$db->indexExists('image','PRIMARY') ) {
                        wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
@@ -1671,8 +1722,8 @@ function wfDeprecatedThumbDir( $thumbName , $subdir='thumb', $shared=false) {
  * @access public
  */
 function wfImageArchiveDir( $fname , $subdir='archive', $shared=false ) {
-       global $wgUploadDirectory, $wgHashedUploadDirectory,
-              $wgSharedUploadDirectory, $wgHashedSharedUploadDirectory;
+       global $wgUploadDirectory, $wgHashedUploadDirectory;
+       global $wgSharedUploadDirectory, $wgHashedSharedUploadDirectory;
        $dir = $shared ? $wgSharedUploadDirectory : $wgUploadDirectory;
        $hashdir = $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
        if (!$hashdir) { return $dir.'/'.$subdir; }
@@ -1876,4 +1927,22 @@ class ThumbnailImage {
        }
 
 }
+
+/**
+ * Calculate the largest thumbnail width for a given original file size
+ * such that the thumbnail's height is at most $maxHeight.
+ * @param int $boxWidth
+ * @param int $boxHeight
+ * @param int $maxHeight
+ * @return int
+ */
+function wfFitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
+       $idealWidth = $boxWidth * $maxHeight / $boxHeight;
+       $roundedUp = ceil( $idealWidth );
+       if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight )
+               return floor( $idealWidth );
+       else
+               return $roundedUp;
+}
+
 ?>